#include <pspiofilemgr.h>
#include "isoloader.h"
#include "debug.h"

void print_to_screen(char*);
void mysprintf1(char *xobuff, const char *xifmt, u32 xidata);

//#define ISO_FILE "ms0:/ISO/wipeoutpulse.iso"
#define ISO_PATH "ms0:/ISO/wipeoutpulse"

int ExtractFile(const char* _src, const char* dst);
void PatchModuleStart();
void prePatch();
void doPatch();
int iso_module_start(int args, void* argp);
int (*real_module_start)(int, void*) = NULL;


ISO* iso = NULL;
SceUID IsoFd;
IsoModule* main_module = NULL;

int realIoOpen(const char* _file, int mode, int rights);
int realIoDopen(const char* _dir);
int realIoMkdir(const char* dir, SceMode mode);
int realIoRmdir(const char *dir);
int realIoDevctl(const char* dev, unsigned int cmd, void* indata, int inlen, void* outdata, int outlen);
int realIoGetstat(const char* file, SceIoStat* stat);
int realIoRemove(const char* file);

int realKernelLoadModule(const char* path, int flags, SceKernelLMOption* option);
int realKernelStartModule(SceUID, int, void*, void*, void*);
u32 IoOpen = 0x0;
u32 IoDopen = 0x0;
u32 IoMkdir = 0x0;
u32 IoRmdir = 0x0;
u32 IoDevctl = 0x0;
u32 IoGetstat = 0x0;
u32 IoRemove = 0x0;
u32 KernelLoadModule = 0x0;
u32 KernelCreateThread = 0x0;
u32 KernelStartThread = 0x0;

int thread(int args, void* argp){
	return 0;
}

void run_iso(){
	LOGSTR0("OpenIdea ISO Loader started !\n");
	IsoModuleManagerInit();

	LOGSTR0("Loading EBOOT_dec.BIN...");
	SceUID modid = IsoLoadModule("ms0:/ISO/wipeoutpulse/PSP_GAME/SYSDIR/EBOOT_dec.BIN");
	LOGSTR1("IsoLoadModule returned 0x%08lX\n", modid);
	main_module = IsoGetModuleByUID(modid);

	PatchModuleStart();

	int ret = IsoStartModule(modid, sizeof("disc0:/PSP_GAME/SYSDIR/EBOOT.BIN"), "disc0:/PSP_GAME/SYSDIR/EBOOT.BIN");
	LOGSTR1("IsoStartModule returned 0x%08lX\n", ret);

	LOGSTR0("Finished !\n");
	return;
}


char real_path[512] = "";
int sceIoOpenPatched(const char* _file, int mode, int rights){
	char* file = (char*)_file;
	LOGSTR3("sceIoOpenPatched(\"%s\", %d, %d)\n", (int)file, mode, rights);
	if(file[0] == 'd'){
		file = file + 6;
		strcpy(real_path, ISO_PATH);
		strcat(real_path, file);
		file = real_path;
	}
	LOGSTR3("  realIoOpen(\"%s\", %d, %d)\n", (int)file, mode, rights);
	return realIoOpen(file, mode, rights);
}

int sceIoDopenPatched(const char* _dir){
	char* dir = (char*)_dir;
	LOGSTR1("sceIoDopenPatched(\"%s\")\n", (int)dir);
	if(dir[0] == 'd'){
		dir = dir + 6;
		strcpy(real_path, ISO_PATH);
		strcat(real_path, dir);
		dir = real_path;
	}
	LOGSTR1("  realIoDopen(\"%s\")\n", (int)dir);
	return realIoDopen(dir);
}

int sceIoMkdirPatched(const char* dir, SceMode mode){
	LOGSTR2("realIoMkdir(\"%s\", %d)\n", (int)dir, mode);
	return realIoMkdir(dir, mode);
}

int sceIoRmdirPatched(const char *dir){
	LOGSTR1("realIoRmdir(\"%s\")\n", (int)dir);
	return realIoRmdir(dir);
}

int sceIoDevctlPatched(const char* dev, unsigned int cmd, void* indata, int inlen, void* outdata, int outlen){
	LOGSTR8("realIoDevctl(\"%s\", 0x%08lX, 0x%08lX, %d, 0x%08lX, %d)\n", (int)dev, cmd, (int)indata, inlen, (int)outdata, outlen, 0, 0);
	return realIoDevctl(dev, cmd, indata, inlen, outdata, outlen);
}

int sceIoGetstatPatched(const char* _file, SceIoStat* stat){
	char* file = (char*)_file;
	LOGSTR2("sceIoGetstatPatched(\"%s\", 0x%08lX)\n", (int)file, (u32)stat);
	if(file[0] == 'd'){
		file = file + 6;
		strcpy(real_path, ISO_PATH);
		strcat(real_path, file);
		file = real_path;
	}
	LOGSTR2("  realIoGetstat(\"%s\", 0x%08lX)\n", (int)file, (u32)stat);
	return realIoGetstat(file, stat);
}

int sceIoRemovePatched(const char* file){
	LOGSTR1("realIoRemove(\"%s\")\n", (int)file);
	return realIoRemove(file);
}

int sceKernelLoadModulePatched(const char* _path, int flags, SceKernelLMOption* option){
	char* path = (char*)_path;
	LOGSTR3("sceKernelLoadModulePatched(\"%s\")\n", (int)path, flags, (int)option);
	if(path[0] == 'd'){
		path = path + 6;
		strcpy(real_path, ISO_PATH);
		strcat(real_path, path);
		path = real_path;
	}
	LOGSTR3("  realKernelLoadModule(\"%s\")\n", (int)path, flags, (int)option);
	return realKernelLoadModule(path, flags, option);
}

SceUID sceKernelCreateThreadPatched(const char* name, void* entry, int prior, int stackSize, int attr, SceKernelThreadOptParam* option){
	LOGSTR8("sceKernelCreateThreadPatched(0x%08lX, 0x%08lX, %d, 0x%08lX, 0x%08lX, 0x%08lX)\n", (int)name, (int)entry, prior, stackSize, (int)option, 0, 0, 0);
	return sceKernelCreateThread(name, entry, prior, stackSize, attr, option);
}

int sceKernelStartThreadPatched(SceUID thid, int args, void *argp){
	LOGSTR3("sceKernelStartThreadPatched(0x%08lX, 0x%08lX, 0x%08lX)\n", thid, args, (int)argp);
	return sceKernelStartThread(thid, args, argp);
}


int sceUmdDeactivatePatched(int UNUSED(unit), char* UNUSED(drive)){
	LOGSTR0("sceUmdDeactivatePatched\n");
	return 0;
}
int sceUmdActivatePatched(int UNUSED(unit), char* UNUSED(drive)){
	LOGSTR0("sceUmdActivatePatched\n");
	return 0;
}
int sceUmdCheckMediumPatched(){
	LOGSTR0("sceUmdCheckMediumPatched\n");
	return 0;
}
int sceUmdWaitDriveStatPatched(int UNUSED(arg0)){
	LOGSTR0("sceUmdWaitDriveStatPatched\n");
	return 0;
}
int sceUmdWaitDriveStatWithTimerPatched(int UNUSED(stat), unsigned int UNUSED(timeout)){
	LOGSTR0("sceUmdWaitDriveStatWithTimerPatched\n");
	return 0;
}
int sceUmdWaitDriveStatCBPatched(int UNUSED(stat), unsigned int UNUSED(timeout)){
	LOGSTR0("sceUmdWaitDriveStatCBPatched\n");
	return 0;
}
int sceUmdGetDriveStatPatched(){
	LOGSTR0("sceUmdGetDriveStatPatched\n");
	return SCE_UMD_MEDIA_IN | SCE_UMD_READY | SCE_UMD_READABLE;
}
int sceUmdGetErrorStatPatched(){
	LOGSTR0("sceUmdGetErrorStatPatched\n");
	return 0;
}

void PatchJump(u32 patch, u32 addr){
	if(!addr)return;
	LOGSTR2("  Patching "#addr" (0x%08lX) to jump to 0x%08lX\n", (int)addr, (int)patch);
	_sw(MAKE_JUMP(patch), addr); _sw(0x00000000, addr+4);
}

void PatchModuleStart(){
	LOGSTR0("==> Enter PatchModuleStart\n");
	real_module_start = (void*)main_module->entry_addr;
	
	IoOpen = FindImport(main_module, "IoFileMgrForUser", 0x109F50BC);
	IoDopen = FindImport(main_module, "IoFileMgrForUser", 0xB29DDF9C);
	IoMkdir = FindImport(main_module, "IoFileMgrForUser", 0x06A70004);
	IoRmdir = FindImport(main_module, "IoFileMgrForUser", 0x1117C65F);
	IoDevctl = FindImport(main_module, "IoFileMgrForUser", 0x54F5FB11);
	IoGetstat = FindImport(main_module, "IoFileMgrForUser", 0xACE946E8);
	IoRemove = FindImport(main_module, "IoFileMgrForUser", 0xF27A9C51);
	KernelLoadModule = FindImport(main_module, "ModuleMgrForUser", 0x977DE386);
	KernelCreateThread = FindImport(main_module, "ThreadManForUser", 0x446D8DE6);
	KernelStartThread = FindImport(main_module, "ThreadManForUser", 0xF475845D);

	u32 _SysMemUserForUser_342061E5 = FindImport(main_module, "SysMemUserForUser", 0x342061E5);
	LOGSTR2("  SysMemUserForUser_342061E5 call: 0x%08lX 0x%08lX\n", ((u32*)_SysMemUserForUser_342061E5)[0], ((u32*)_SysMemUserForUser_342061E5)[1]);

	PatchJump(sceIoOpenPatched, IoOpen);
	PatchJump(sceIoDopenPatched, IoDopen);
	PatchJump(sceIoMkdirPatched, IoMkdir);
	PatchJump(sceIoRmdirPatched, IoRmdir);
	PatchJump(sceIoDevctlPatched, IoDevctl);
	PatchJump(sceIoGetstatPatched, IoGetstat);
	PatchJump(sceIoRemovePatched, IoRemove);
	PatchJump(sceKernelLoadModulePatched, KernelLoadModule);
	PatchJump(sceKernelCreateThreadPatched, KernelCreateThread);
	PatchJump(sceKernelStartThreadPatched, KernelStartThread);

	
	PatchJump(sceUmdActivatePatched, FindImport(main_module, "sceUmdUser", 0xC6183D47));
	PatchJump(sceUmdDeactivatePatched, FindImport(main_module, "sceUmdUser", 0xE83742BA));
	PatchJump(sceUmdCheckMediumPatched, FindImport(main_module, "sceUmdUser", 0x46EBB729));
	PatchJump(sceUmdWaitDriveStatWithTimerPatched, FindImport(main_module, "sceUmdUser", 0x56202973));
	PatchJump(sceUmdWaitDriveStatCBPatched, FindImport(main_module, "sceUmdUser", 0x4A9E5E29));
	PatchJump(sceUmdGetDriveStatPatched, FindImport(main_module, "sceUmdUser", 0x6B4A146C));
	PatchJump(sceUmdGetErrorStatPatched, FindImport(main_module, "sceUmdUser", 0x20628E6F));
	PatchJump(sceUmdWaitDriveStatPatched, FindImport(main_module, "sceUmdUser", 0x8EF08FCE));

	_sw(0xAC000000, (u32)(((u8*)real_module_start)+0x00000048)); // If load error
//	_sw(0xAC000000, (u32)(((u8*)real_module_start)+0x000000D8));
	LOGSTR1("  real_module_start: 0x%08lX\n", (u32)real_module_start);

	LOGSTR0("==> Exit PatchModuleStart\n");
}

static int n_alloc = 0;
static int n_free = 0;

void* geMalloc(int size){
	n_alloc++;
	u32 id = sceKernelAllocPartitionMemory(2, "alloc", 0, size+8, NULL);
	u32* var = (u32*)sceKernelGetBlockHeadAddr(id);
	var[0] = id; //Store the ID in the first byte
	var[1] = size; //Store the size on the 2nd byte
	return var+8; //Then return block +2 (id + size)
}

void geFree(void* data){
	n_free++;
	u32* var = (u32*)data - 2; //Get the real first byte
	u32 id = var[0]; //The get the ID
	sceKernelFreePartitionMemory(id); //We can geFree the data
}

void* geRealloc(void* last, int size){
	u32 old_size = ((u32*)last)[1]; //The size is stored in the 2nd byte
	u32* new_ptr = (u32*)geMalloc(size); //Do a geMalloc with new size
	memcpy(new_ptr, ((u32*)last), old_size); //Copy the lod data
	geFree(last); //geFree the last block
	return new_ptr; //Then return the new pointer
}
